/*
 * Copyright 2011 Katsunori Koyanagi
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */

package sweezy.swf.decoder
{
	import flash.utils.ByteArray;
	import flash.utils.Endian;
	import sweezy.core.utils.Messages;
	import sweezy.swf.ITag;
	import sweezy.swf.SWF;
	import sweezy.swf.constant.AlphaBitmapType;
	import sweezy.swf.constant.BitmapType;
	import sweezy.swf.constant.CompressType;
	import sweezy.swf.constant.FillStyleType;
	import sweezy.swf.constant.FilterType;
	import sweezy.swf.constant.JoinStyle;
	import sweezy.swf.constant.MorphFillStyleType;
	import sweezy.swf.constant.SWFVersion;
	import sweezy.swf.constant.SoundFormat;
	import sweezy.swf.io.SWFInput;
	import sweezy.swf.messages.SWFDecoderMessages;
	import sweezy.swf.structure.ABCData;
	import sweezy.swf.structure.ActionRecords;
	import sweezy.swf.structure.AlphaBitmapData;
	import sweezy.swf.structure.AlphaColorMapData;
	import sweezy.swf.structure.AlphaImageData;
	import sweezy.swf.structure.Asset;
	import sweezy.swf.structure.BevelFilter;
	import sweezy.swf.structure.BitmapData15;
	import sweezy.swf.structure.BitmapData24;
	import sweezy.swf.structure.BlurFilter;
	import sweezy.swf.structure.ButtonCondAction;
	import sweezy.swf.structure.ButtonRecord;
	import sweezy.swf.structure.CXForm;
	import sweezy.swf.structure.CXFormWithAlpha;
	import sweezy.swf.structure.ClipActionRecord;
	import sweezy.swf.structure.ClipActions;
	import sweezy.swf.structure.ClipEventFlags;
	import sweezy.swf.structure.ColorMapData;
	import sweezy.swf.structure.ColorMatrixFilter;
	import sweezy.swf.structure.ConvolutionFilter;
	import sweezy.swf.structure.CurvedEdgeRecord;
	import sweezy.swf.structure.DropShadowFilter;
	import sweezy.swf.structure.EndShapeRecord;
	import sweezy.swf.structure.FillStyle;
	import sweezy.swf.structure.FocalGradient;
	import sweezy.swf.structure.FrameLabelData;
	import sweezy.swf.structure.GlowFilter;
	import sweezy.swf.structure.GlyphEntry;
	import sweezy.swf.structure.Gradient;
	import sweezy.swf.structure.GradientBevelFilter;
	import sweezy.swf.structure.GradientGlowFilter;
	import sweezy.swf.structure.GradientRecord;
	import sweezy.swf.structure.Header;
	import sweezy.swf.structure.ImageData;
	import sweezy.swf.structure.KerningRecord;
	import sweezy.swf.structure.LanguageCode;
	import sweezy.swf.structure.LineStyle;
	import sweezy.swf.structure.LineStyle2;
	import sweezy.swf.structure.Matrix;
	import sweezy.swf.structure.MorphFillStyle;
	import sweezy.swf.structure.MorphGradientRecord;
	import sweezy.swf.structure.MorphLineStyle;
	import sweezy.swf.structure.MorphLineStyle2;
	import sweezy.swf.structure.RGB;
	import sweezy.swf.structure.RGBA;
	import sweezy.swf.structure.Rect;
	import sweezy.swf.structure.SceneData;
	import sweezy.swf.structure.Shape;
	import sweezy.swf.structure.ShapeWithStyle;
	import sweezy.swf.structure.SoundEnvelope;
	import sweezy.swf.structure.SoundInfo;
	import sweezy.swf.structure.StraightEdgeRecord;
	import sweezy.swf.structure.StyleChangeRecord;
	import sweezy.swf.structure.Symbol;
	import sweezy.swf.structure.TextRecord;
	import sweezy.swf.structure.ZoneData;
	import sweezy.swf.structure.ZoneRecord;
	import sweezy.swf.tag.CSMTextSettings;
	import sweezy.swf.tag.DebugID;
	import sweezy.swf.tag.DefineBinaryData;
	import sweezy.swf.tag.DefineBits;
	import sweezy.swf.tag.DefineBitsJPEG2;
	import sweezy.swf.tag.DefineBitsJPEG3;
	import sweezy.swf.tag.DefineBitsJPEG4;
	import sweezy.swf.tag.DefineBitsLossless;
	import sweezy.swf.tag.DefineBitsLossless2;
	import sweezy.swf.tag.DefineButton;
	import sweezy.swf.tag.DefineButton2;
	import sweezy.swf.tag.DefineButtonCxform;
	import sweezy.swf.tag.DefineButtonSound;
	import sweezy.swf.tag.DefineEditText;
	import sweezy.swf.tag.DefineFont;
	import sweezy.swf.tag.DefineFont2;
	import sweezy.swf.tag.DefineFont23Base;
	import sweezy.swf.tag.DefineFont3;
	import sweezy.swf.tag.DefineFont4;
	import sweezy.swf.tag.DefineFontAlignZones;
	import sweezy.swf.tag.DefineFontInfo;
	import sweezy.swf.tag.DefineFontInfo2;
	import sweezy.swf.tag.DefineFontName;
	import sweezy.swf.tag.DefineMorphShape;
	import sweezy.swf.tag.DefineMorphShape2;
	import sweezy.swf.tag.DefineScalingGrid;
	import sweezy.swf.tag.DefineSceneAndFrameLabelData;
	import sweezy.swf.tag.DefineShape;
	import sweezy.swf.tag.DefineShape2;
	import sweezy.swf.tag.DefineShape3;
	import sweezy.swf.tag.DefineShape4;
	import sweezy.swf.tag.DefineSound;
	import sweezy.swf.tag.DefineSprite;
	import sweezy.swf.tag.DefineText;
	import sweezy.swf.tag.DefineText2;
	import sweezy.swf.tag.DefineTextBase;
	import sweezy.swf.tag.DefineVideoStream;
	import sweezy.swf.tag.DoABC;
	import sweezy.swf.tag.DoABC2;
	import sweezy.swf.tag.DoAction;
	import sweezy.swf.tag.DoInitAction;
	import sweezy.swf.tag.EnableDebugger;
	import sweezy.swf.tag.EnableDebugger2;
	import sweezy.swf.tag.End;
	import sweezy.swf.tag.ExportAssets;
	import sweezy.swf.tag.FileAttributes;
	import sweezy.swf.tag.FrameLabel;
	import sweezy.swf.tag.ImportAssets;
	import sweezy.swf.tag.ImportAssets2;
	import sweezy.swf.tag.JPEGTables;
	import sweezy.swf.tag.Metadata;
	import sweezy.swf.tag.PlaceObject;
	import sweezy.swf.tag.PlaceObject2;
	import sweezy.swf.tag.PlaceObject3;
	import sweezy.swf.tag.ProductInfo;
	import sweezy.swf.tag.Protect;
	import sweezy.swf.tag.RemoveObject;
	import sweezy.swf.tag.RemoveObject2;
	import sweezy.swf.tag.ScriptLimits;
	import sweezy.swf.tag.SetBackgroundColor;
	import sweezy.swf.tag.SetTabIndex;
	import sweezy.swf.tag.ShowFrame;
	import sweezy.swf.tag.SoundStreamBlock;
	import sweezy.swf.tag.SoundStreamHead;
	import sweezy.swf.tag.SoundStreamHead2;
	import sweezy.swf.tag.StartSound;
	import sweezy.swf.tag.StartSound2;
	import sweezy.swf.tag.SymbolClass;
	import sweezy.swf.tag.Tag;
	import sweezy.swf.tag.UnknownTag;
	import sweezy.swf.tag.VideoFrame;

	public class SWFDecoder
	{

		private static var _msg:Messages = Messages.getInstance(SWFDecoderMessages);

		private static var _skippedTag:ITag;

		private var _errors:Array;

		private var _in:SWFInput;

		private var _tagFilterFunction:Function;

		private var _tagHandler:Function;

		private var _tagStack:Array;

		private var _version:uint;

		public function SWFDecoder()
		{
			if (_skippedTag === null)
			{
				_skippedTag = new SkippedTag();
			}
			_tagStack = [];
		}

		public function decode(bytes:ByteArray):SWF
		{
			_errors = null;
			_in = new SWFInput(bytes);

			var swf:SWF = new SWF();
			swf.tags = [];
			swf.header = decodeHeader();
			if (swf.header === null)
			{
				return swf;
			}
			_version = swf.header.version;

			var tags:Array = swf.tags;
			while (true)
			{
				if (_in.eof)
				{
					break;
				}

				var tag:ITag = null;
				try
				{
					tag = decodeTag(_tagFilterFunction);
					if (tag is SkippedTag)
					{
						continue;
					}
				}
				catch (e:Error)
				{
					notifyError(_msg.format("000", e.message));
					break;
				}

				if (tag === null)
				{
					continue;
				}

				tags[tags.length] = tag;
				if (_tagHandler !== null)
				{
					_tagHandler(tag);
				}
			}

			_in = null;
			return swf;
		}

		public function get errors():Array
		{
			return _errors;
		}

		public function get hasErrors():Boolean
		{
			return _errors !== null;
		}

		public function get tagFilterFunction():Function
		{
			return _tagFilterFunction;
		}

		public function set tagFilterFunction(value:Function):void
		{
			_tagFilterFunction = value;
		}

		public function get tagHandler():Function
		{
			return _tagHandler;
		}

		public function set tagHandler(value:Function):void
		{
			_tagHandler = value;
		}

		private function get currentTag():int
		{
			if (_tagStack.length === 0)
			{
				return -1;
			}

			return _tagStack[_tagStack.length - 1];
		}

		private function decodeActionRecords(length:uint):ActionRecords
		{
			var actionRecords:ActionRecords = new ActionRecords();
			var bytes:ByteArray = _in.readBytes(length);
			var decoder:ActionDecoder = new ActionDecoder();
			var records:Array = null;

			try
			{
				records = decoder.decode(_version, bytes);
			}
			catch (e:Error)
			{
				notifyError(_msg.format("006", e.message));
			}

			actionRecords.records = records === null ? [] : records;
			return actionRecords;
		}

		private function decodeButtonRecords():Array
		{
			var records:Array = [];
			var button2:Boolean = currentTag === Tag.DEFINE_BUTTON_2;
			var idx:int = 0;

			while (true)
			{
				_in.resetCursor();
				var reserved:uint = _in.readUB(2);
				var buttonHasBlendMode:Boolean = _in.readFLAG();
				var buttonHasFilterList:Boolean = _in.readFLAG();
				var buttonStateHitTest:Boolean = _in.readFLAG();
				var buttonStateDown:Boolean = _in.readFLAG();
				var buttonStateOver:Boolean = _in.readFLAG();
				var buttonStateUp:Boolean = _in.readFLAG();

				if (buttonHasBlendMode || buttonHasFilterList || buttonStateHitTest || buttonStateDown || buttonStateOver || buttonStateUp)
				{
					var record:ButtonRecord = new ButtonRecord();
					record.defineButton2 = button2;
					record.buttonHasBlendMode = buttonHasBlendMode;
					record.buttonHasFilterList = buttonHasFilterList;
					record.buttonStateDown = buttonStateDown;
					record.buttonStateHitTest = buttonStateHitTest;
					record.buttonStateOver = buttonStateOver;
					record.buttonStateUp = buttonStateUp;
					record.characterId = _in.readUI16();
					record.placeDepth = _in.readUI16();
					record.placeMatrix = decodeMatrix();

					if (button2)
					{
						record.colorTransform = decodeCXFormWithAlpha();
					}
					if (button2 && buttonHasFilterList)
					{
						record.filterList = decodeFilterList();
					}
					if (button2 && buttonHasBlendMode)
					{
						record.blendMode = _in.readUI8();
					}
					records[idx++] = record;
				}
				else
				{
					break;
				}
			}

			return records;
		}

		private function decodeCXForm():CXForm
		{
			_in.resetCursor();

			var cxForm:CXForm = new CXForm();
			cxForm.hasAdd = _in.readFLAG();
			cxForm.hasMult = _in.readFLAG();
			var len:uint = _in.readUB(4);
			cxForm.minBits = len;

			if (cxForm.hasMult)
			{
				cxForm.redMultTerm = _in.readSB(len);
				cxForm.greenMultTerm = _in.readSB(len);
				cxForm.blueMultTerm = _in.readSB(len);
			}
			if (cxForm.hasAdd)
			{
				cxForm.redAddTerm = _in.readSB(len);
				cxForm.greenAddTerm = _in.readSB(len);
				cxForm.blueAddTerm = _in.readSB(len);
			}

			return cxForm;
		}

		private function decodeCXFormWithAlpha():CXFormWithAlpha
		{
			_in.resetCursor();

			var cxForm:CXFormWithAlpha = new CXFormWithAlpha();
			cxForm.hasAdd = _in.readFLAG();
			cxForm.hasMult = _in.readFLAG();
			var len:uint = _in.readUB(4);
			cxForm.minBits = len;

			if (cxForm.hasMult)
			{
				cxForm.redMultTerm = _in.readSB(len);
				cxForm.greenMultTerm = _in.readSB(len);
				cxForm.blueMultTerm = _in.readSB(len);
				cxForm.alphaMultTerm = _in.readSB(len);
			}
			if (cxForm.hasAdd)
			{
				cxForm.redAddTerm = _in.readSB(len);
				cxForm.greenAddTerm = _in.readSB(len);
				cxForm.blueAddTerm = _in.readSB(len);
				cxForm.alphaAddTerm = _in.readSB(len);
			}

			return cxForm;
		}

		private function decodeClipActions():ClipActions
		{
			var clipActions:ClipActions = new ClipActions();
			_in.readUI16();
			clipActions.allEventFlags = decodeClipEventFlags();
			var data:Array = clipActions.clipActionRecords = [];
			var idx:int = 0;

			while (true)
			{
				var eventFlags:ClipEventFlags = decodeClipEventFlags();
				if (eventFlags.clipEventKeyUp || eventFlags.clipEventKeyDown || eventFlags.clipEventMouseUp || eventFlags.clipEventMouseDown || eventFlags.clipEventMouseMove || eventFlags.clipEventUnload || eventFlags.clipEventEnterFrame || eventFlags.clipEventLoad || eventFlags.clipEventDragOver || eventFlags.clipEventRollOut || eventFlags.clipEventRollOver || eventFlags.clipEventReleaseOutside || eventFlags.clipEventRelease || eventFlags.clipEventPress || eventFlags.clipEventInitialize || eventFlags.clipEventData || eventFlags.clipEventConstruct || eventFlags.clipEventKeyPress || eventFlags.clipEventDragOut)
				{
					var clipActionRecord:ClipActionRecord = new ClipActionRecord();
					clipActionRecord.eventFlags = eventFlags;
					var size:uint = _in.readUI32();
					if (eventFlags.clipEventKeyPress)
					{
						clipActionRecord.keyCode = _in.readUI8();
					}
					clipActionRecord.actions = decodeActionRecords(size);
					data[idx++] = clipActionRecord;
				}
				else
				{
					break;
				}
			}

			return clipActions;
		}

		private function decodeClipEventFlags():ClipEventFlags
		{
			var clipEventFlags:ClipEventFlags = new ClipEventFlags();
			_in.resetCursor();
			clipEventFlags.clipEventKeyUp = _in.readFLAG();
			clipEventFlags.clipEventKeyDown = _in.readFLAG();
			clipEventFlags.clipEventMouseUp = _in.readFLAG();
			clipEventFlags.clipEventMouseDown = _in.readFLAG();
			clipEventFlags.clipEventMouseMove = _in.readFLAG();
			clipEventFlags.clipEventUnload = _in.readFLAG();
			clipEventFlags.clipEventEnterFrame = _in.readFLAG();
			clipEventFlags.clipEventLoad = _in.readFLAG();
			clipEventFlags.clipEventDragOver = _in.readFLAG();
			clipEventFlags.clipEventRollOut = _in.readFLAG();
			clipEventFlags.clipEventRollOver = _in.readFLAG();
			clipEventFlags.clipEventReleaseOutside = _in.readFLAG();
			clipEventFlags.clipEventRelease = _in.readFLAG();
			clipEventFlags.clipEventPress = _in.readFLAG();
			clipEventFlags.clipEventInitialize = _in.readFLAG();
			clipEventFlags.clipEventData = _in.readFLAG();

			if (_version >= SWFVersion.VERSION_6)
			{
				_in.readUB(5);
				clipEventFlags.clipEventConstruct = _in.readFLAG();
				clipEventFlags.clipEventKeyPress = _in.readFLAG();
				clipEventFlags.clipEventDragOut = _in.readFLAG();
				_in.readUB(8);
			}

			return clipEventFlags;
		}

		private function decodeDefineFont2OR3(length:uint, tag:DefineFont23Base):void
		{
			_in.pushPosition();

			tag.fontId = _in.readUI16();
			tag.hasLayout = _in.readFLAG();
			tag.shiftJIS = _in.readFLAG();
			tag.smallText = _in.readFLAG();
			tag.ansi = _in.readFLAG();
			tag.wideOffset = _in.readFLAG();
			tag.wideCodes = _in.readFLAG();
			tag.italic = _in.readFLAG();
			tag.bold = _in.readFLAG();
			tag.languageCode = decodeLanguageCode();
			tag.fontName = decodeString(_in.readUI8());

			var numGlyphs:uint = tag.numGlyphs = _in.readUI16();
			var data:Array, i:int;
			tag.offsetTable = data = [];
			if (tag.wideOffset)
			{
				for (i = 0; i < numGlyphs; i++)
				{
					data[i] = _in.readUI32();
				}
			}
			else
			{
				for (i = 0; i < numGlyphs; i++)
				{
					data[i] = _in.readUI16();
				}
			}

			if (_in.popPosition(false) >= length)
			{
				tag.glyphShapeTable = [];
				tag.codeTable = [];
				return;
			}

			var codeTableOffset:uint = tag.wideOffset ? _in.readUI32() : _in.readUI16();
			tag.glyphShapeTable = data = [];
			for (i = 0; i < numGlyphs; i++)
			{
				data[i] = decodeShape();
			}

			tag.codeTable = data = [];
			if (tag.wideCodes)
			{
				for (i = 0; i < numGlyphs; i++)
				{
					data[i] = _in.readUI16();
				}
			}
			else
			{
				for (i = 0; i < numGlyphs; i++)
				{
					data[i] = _in.readUI8();
				}
			}

			if (tag.hasLayout)
			{
				tag.ascent = _in.readSI16();
				tag.descent = _in.readSI16();
				tag.leading = _in.readSI16();

				tag.advanceTable = data = [];
				for (i = 0; i < numGlyphs; i++)
				{
					data[i] = _in.readSI16();
				}

				tag.boundsTable = data = [];
				for (i = 0; i < numGlyphs; i++)
				{
					data[i] = decodeRect();
				}

				var kerningCount:uint = _in.readUI16();
				tag.kerningTable = data = [];
				for (i = 0; i < kerningCount; i++)
				{
					data[i] = decodeKerningRecord(tag.wideCodes);
				}
			}
		}

		private function decodeDefineText1Or2(length:uint, tag:DefineTextBase):void
		{
			tag.characterId = _in.readUI16();
			tag.textBounds = decodeRect();
			tag.textMatrix = decodeMatrix();
			tag.glyphBits = _in.readUI8();
			tag.advanceBits = _in.readUI8();

			var data:Array = tag.textRecords = [];
			var text2:Boolean = currentTag === Tag.DEFINE_TEXT_2;
			var idx:int = 0;

			while (true)
			{
				_in.resetCursor();
				if (!_in.readFLAG())
				{
					_in.resetCursor();
					break;
				}
				_in.readUB(3);

				var textRecord:TextRecord = new TextRecord();
				data[idx++] = textRecord;

				textRecord.hasFont = _in.readFLAG();
				textRecord.hasColor = _in.readFLAG();
				textRecord.hasYOffset = _in.readFLAG();
				textRecord.hasXOffset = _in.readFLAG();

				if (textRecord.hasFont)
				{
					textRecord.fontId = _in.readUI16();
				}
				if (textRecord.hasColor)
				{
					if (text2)
					{
						textRecord.textColor = decodeRGBA();
					}
					else
					{
						textRecord.textColor = decodeRGB();
					}
				}
				if (textRecord.hasXOffset)
				{
					textRecord.xOffset = _in.readSI16();
				}
				if (textRecord.hasYOffset)
				{
					textRecord.yOffset = _in.readSI16();
				}
				if (textRecord.hasFont)
				{
					textRecord.textHeight = _in.readUI16();
				}

				var glyphCount:uint = _in.readUI8();
				var glyphs:Array = textRecord.glyphEntries = [];

				for (var i:int = 0; i < glyphCount; i++)
				{
					var glyphEntry:GlyphEntry = new GlyphEntry();
					glyphs[i] = glyphEntry;
					glyphEntry.glyphIndex = _in.readUB(tag.glyphBits);
					glyphEntry.glyphAdvance = _in.readSB(tag.advanceBits);
				}
			}
		}

		private function decodeFillStyle():FillStyle
		{
			var fillStyle:FillStyle = new FillStyle();
			var type:uint = fillStyle.fillStyleType = _in.readUI8();
			var tag:int = currentTag;

			switch (type)
			{
				case FillStyleType.SOLID_FILL:
					if (tag === Tag.DEFINE_SHAPE || tag === Tag.DEFINE_SHAPE_2)
					{
						fillStyle.color = decodeRGB();
					}
					else
					{
						fillStyle.color = decodeRGBA();
					}
					break;
				case FillStyleType.LINEAR_GRADIENT_FILL:
				case FillStyleType.RADIAL_GRADIENT_FILL:
				case FillStyleType.FOCAL_RADIAL_GRADIENT_FILL:
					fillStyle.gradientMatrix = decodeMatrix();
					if (type === FillStyleType.FOCAL_RADIAL_GRADIENT_FILL)
					{
						fillStyle.gradient = decodeFocalGradient();
					}
					else
					{
						fillStyle.gradient = decodeGradient();
					}
					break;
				case FillStyleType.REPEATING_BITMAP_FILL:
				case FillStyleType.CLIPPED_BITMAP_FILL:
				case FillStyleType.NON_SMOOTHED_CLIPPED_BITMAP_FILL:
				case FillStyleType.NON_SMOOTHED_REPEATING_BITMAP_FILL:
					fillStyle.bitmapId = _in.readUI16();
					fillStyle.bitmapMatrix = decodeMatrix();
					break;
			}

			return fillStyle;
		}

		private function decodeFillStyleArray():Array
		{
			var fillStyles:Array = [];
			var idx:uint = 0;
			var len:uint = _in.readUI8();
			if (len === 0xff)
			{
				len = _in.readUI16();
			}

			for (var i:int = 0; i < len; i++)
			{
				fillStyles[idx++] = decodeFillStyle();
			}

			return fillStyles;
		}

		private function decodeFilterList():Array
		{
			var filters:Array = [];
			var count:uint = _in.readUI8();
			var j:int, len:int, data:Array, data2:Array;

			for (var i:int = 0; i < count; i++)
			{
				_in.resetCursor();
				switch (_in.readUI8())
				{
					case FilterType.DROP_SHADOW_FILTER:
						var filter0:DropShadowFilter = new DropShadowFilter();
						filter0.dropShadowColor = decodeRGBA();
						filter0.blurX = _in.readFIXED();
						filter0.blurY = _in.readFIXED();
						filter0.angle = _in.readFIXED();
						filter0.distance = _in.readFIXED();
						filter0.strength = _in.readFIXED8();
						filter0.innerShadow = _in.readFLAG();
						filter0.knockout = _in.readFLAG();
						filter0.compositeSource = _in.readFLAG();
						filter0.passes = _in.readUB(5);

						filters[i] = filter0;
						break;
					case FilterType.BLUR_FILTER:
						var filter1:BlurFilter = new BlurFilter();
						filter1.blurX = _in.readFIXED();
						filter1.blurY = _in.readFIXED();
						filter1.passes = _in.readUB(5);
						_in.readUB(3);

						filters[i] = filter1;
						break;
					case FilterType.GLOW_FILTER:
						var filter2:GlowFilter = new GlowFilter();
						filter2.glowColor = decodeRGBA();
						filter2.blurX = _in.readFIXED();
						filter2.blurY = _in.readFIXED();
						filter2.strength = _in.readFIXED8();
						filter2.innerGlow = _in.readFLAG();
						filter2.knockout = _in.readFLAG();
						filter2.compositeSource = _in.readFLAG();
						filter2.passes = _in.readUB(5);

						filters[i] = filter2;
						break;
					case FilterType.BEVEL_FILTER:
						var filter3:BevelFilter = new BevelFilter();
						filter3.shadowColor = decodeRGBA();
						filter3.highlightColor = decodeRGBA();
						filter3.blurX = _in.readFIXED();
						filter3.blurY = _in.readFIXED();
						filter3.angle = _in.readFIXED();
						filter3.distance = _in.readFIXED();
						filter3.strength = _in.readFIXED8();
						filter3.innerShadow = _in.readFLAG();
						filter3.knockout = _in.readFLAG();
						filter3.compositeSource = _in.readFLAG();
						filter3.onTop = _in.readFLAG();
						filter3.passes = _in.readUB(4);

						filters[i] = filter3;
						break;
					case FilterType.GRADIENT_GLOW_FILTER:
						var filter4:GradientGlowFilter = new GradientGlowFilter();
						len = _in.readUI8();
						filter4.gradientColors = data = [];
						filter4.gradientRatio = data2 = [];
						for (j = 0; j < len; j++)
						{
							data[j] = decodeRGBA();
							data2[j] = _in.readUI8();
						}
						filter4.blurX = _in.readFIXED();
						filter4.blurY = _in.readFIXED();
						filter4.angle = _in.readFIXED();
						filter4.distance = _in.readFIXED();
						filter4.strength = _in.readFIXED8();
						filter4.innerShadow = _in.readFLAG();
						filter4.knockout = _in.readFLAG();
						filter4.compositeSource = _in.readFLAG();
						filter4.onTop = _in.readFLAG();
						filter4.passes = _in.readUB(4);

						filters[i] = filter4;
						break;
					case FilterType.CONVOLUTION_FILTER:
						var filter5:ConvolutionFilter = new ConvolutionFilter();
						filter5.matrixX = _in.readUI8();
						filter5.matrixY = _in.readUI8();
						filter5.divisor = _in.readFLOAT32();
						filter5.bias = _in.readFLOAT32();
						filter5.matrix = data = [];
						for (j = 0, len = filter5.matrixX * filter5.matrixY; j < len; j++)
						{
							data[j] = _in.readFLOAT32();
						}
						filter5.defaultColor = decodeRGBA();
						_in.readUB(6);
						filter5.clamp = _in.readFLAG();
						filter5.preserveAlpha = _in.readFLAG();

						filters[i] = filter5;
						break;
					case FilterType.COLOR_MATRIX_FILTER:
						var filter6:ColorMatrixFilter = new ColorMatrixFilter();
						filter6.matrix = data = [];
						for (j = 0; j < 20; j++)
						{
							data[j] = _in.readFLOAT32();
						}

						filters[i] = filter6;
						break;
					case FilterType.GRADIENT_BEVEL_FILTER:
						var filter7:GradientBevelFilter = new GradientBevelFilter();
						len = _in.readUI8();
						filter7.gradientColors = data = [];
						filter7.gradientRatio = data2 = [];
						for (j = 0; j < len; j++)
						{
							data[j] = decodeRGBA();
							data2[j] = _in.readUI8();
						}
						filter7.blurX = _in.readFIXED();
						filter7.blurY = _in.readFIXED();
						filter7.angle = _in.readFIXED();
						filter7.distance = _in.readFIXED();
						filter7.strength = _in.readFIXED8();
						filter7.innerShadow = _in.readFLAG();
						filter7.knockout = _in.readFLAG();
						filter7.compositeSource = _in.readFLAG();
						filter7.onTop = _in.readFLAG();
						filter7.passes = _in.readUB(4);

						filters[i] = filter7;
						break;
				}
			}

			return filters;
		}

		private function decodeFocalGradient():Gradient
		{
			_in.resetCursor();

			var gradient:FocalGradient = new FocalGradient();
			gradient.spreadMode = _in.readUB(2);
			gradient.interpolationMode = _in.readUB(2);
			var len:uint = _in.readUB(4);
			var records:Array = [];
			var idx:uint = 0;

			for (var i:int = 0; i < len; i++)
			{
				records[idx++] = decodeGradientRecord();
			}
			gradient.gradientRecords = records;
			gradient.focalPoint = _in.readFIXED8();

			return gradient;
		}

		private function decodeGradient():Gradient
		{
			_in.resetCursor();

			var gradient:Gradient = new Gradient();
			gradient.spreadMode = _in.readUB(2);
			gradient.interpolationMode = _in.readUB(2);
			var len:uint = _in.readUB(4);
			var records:Array = [];
			var idx:uint = 0;

			for (var i:int = 0; i < len; i++)
			{
				records[idx++] = decodeGradientRecord();
			}
			gradient.gradientRecords = records;

			return gradient;
		}

		private function decodeGradientRecord():GradientRecord
		{
			var gradientRecord:GradientRecord = new GradientRecord();
			gradientRecord.ratio = _in.readUI8();
			var tag:int = currentTag;
			if (tag === Tag.DEFINE_SHAPE || tag === Tag.DEFINE_SHAPE_2)
			{
				gradientRecord.color = decodeRGB();
			}
			else
			{
				gradientRecord.color = decodeRGBA();
			}

			return gradientRecord;
		}

		private function decodeHeader():Header
		{
			var header:Header = new Header();

			switch (_in.readUI8())
			{
				case 0x43:
					header.compressed = true;
					header.compressType = CompressType.ZLIB;
					break;
				case 0x46:
					header.compressed = false;
					header.compressType = CompressType.NONE;
					break;
				/*
				   //TODO LAMA
				   case 0x5A:
				   header.compressed = true;
				   header.compressType = CompressType.LZMA;
				   break;
				 */
				default:
					notifyError(_msg.format("001"));
					return null;
			}
			if (_in.readUI8() !== 0x57 || _in.readUI8() !== 0x53)
			{
				notifyError(_msg.format("002"));
				return null;
			}

			header.version = _in.readUI8();

			var len:int = _in.readUI32();
			var compressed:Boolean;
			try
			{
				compressed = uncompressSWF(header);
			}
			catch (e:Error)
			{
				notifyError(_msg.format("003"));
				return null;
			}

			if (len !== _in.length + (compressed ? 8 : 0))
			{
				notifyError(_msg.format("004"));
				return null;
			}

			header.frameSize = decodeRect();
			header.frameRate = _in.readFIXED8();
			header.frameCount = _in.readUI16();

			return header;
		}

		private function decodeKerningRecord(wideCodes:Boolean):KerningRecord
		{
			var kerningRecord:KerningRecord = new KerningRecord();
			kerningRecord.fontKerningCode1 = wideCodes ? _in.readUI16() : _in.readUI8();
			kerningRecord.fontKerningCode2 = wideCodes ? _in.readUI16() : _in.readUI8();
			kerningRecord.fontKerningAdjustment = _in.readSI16();

			return kerningRecord;
		}

		private function decodeLanguageCode():LanguageCode
		{
			var languageCode:LanguageCode = new LanguageCode();
			languageCode.languageCode = _in.readUI8();

			return languageCode;
		}

		private function decodeLineStyle():LineStyle
		{
			var lineStyle:LineStyle = new LineStyle();
			lineStyle.width = _in.readUI16();
			var tag:int = currentTag;
			if (tag === Tag.DEFINE_SHAPE || tag === Tag.DEFINE_SHAPE_2)
			{
				lineStyle.color = decodeRGB();
			}
			else
			{
				lineStyle.color = decodeRGBA();
			}

			return lineStyle;
		}

		private function decodeLineStyle2():LineStyle2
		{
			var lineStyle2:LineStyle2 = new LineStyle2();
			lineStyle2.width = _in.readUI16();
			lineStyle2.startCapStyle = _in.readUB(2);
			lineStyle2.joinStyle = _in.readUB(2);

			lineStyle2.hasFill = _in.readFLAG()
			lineStyle2.noHScale = _in.readFLAG();
			lineStyle2.noVScale = _in.readFLAG();
			lineStyle2.pixelHinting = _in.readFLAG();
			_in.readUB(5);
			lineStyle2.noClose = _in.readFLAG();
			lineStyle2.endCapStyle = _in.readUB(2);

			if (lineStyle2.joinStyle === JoinStyle.JOIN_STYLE_MITER)
			{
				lineStyle2.miterLimitFactor = _in.readUI16();
			}
			if (!lineStyle2.hasFill)
			{
				lineStyle2.color = decodeRGBA();
			}
			else
			{
				lineStyle2.fillType = decodeFillStyle();
			}

			return lineStyle2;
		}

		private function decodeLineStyleArray():Array
		{
			var lineStyles:Array = [];
			var len:uint = _in.readUI8();
			if (len === 0xff)
			{
				len = _in.readUI16();
			}

			var ls2:Boolean = currentTag === Tag.DEFINE_SHAPE_4;
			var idx:int = 0;
			for (var i:int = 0; i < len; i++)
			{
				if (ls2)
				{
					lineStyles[idx++] = decodeLineStyle2();
				}
				else
				{
					lineStyles[idx++] = decodeLineStyle();
				}
			}

			return lineStyles;
		}

		private function decodeMatrix():Matrix
		{
			_in.resetCursor();

			var matrix:Matrix = new Matrix();
			var len:uint;

			matrix.hasScale = _in.readFLAG();
			if (matrix.hasScale)
			{
				len = _in.readUB(5);
				matrix.scaleMinBits = len;
				matrix.scaleX = _in.readFB(len);
				matrix.scaleY = _in.readFB(len);
			}

			matrix.hasRotate = _in.readFLAG();
			if (matrix.hasRotate)
			{
				len = _in.readUB(5);
				matrix.rotateMinBits = len;
				matrix.rotateSkew0 = _in.readFB(len);
				matrix.rotateSkew1 = _in.readFB(len);
			}
			len = _in.readUB(5);
			matrix.transrateMinBits = len;
			matrix.translateX = _in.readSB(len);
			matrix.translateY = _in.readSB(len);

			return matrix;
		}

		private function decodeMorphFillStyle():MorphFillStyle
		{
			var morphFillStyle:MorphFillStyle = new MorphFillStyle();
			var type:uint = morphFillStyle.fillStyleType = _in.readUI8();
			switch (type)
			{
				case MorphFillStyleType.SOLID_FILL:
					morphFillStyle.startColor = decodeRGBA();
					morphFillStyle.endColor = decodeRGBA();
					break;
				case MorphFillStyleType.LINEAR_GRADIENT_FILL:
				case MorphFillStyleType.RADIAL_GRADIENT_FILL:
					morphFillStyle.startGradientMatrix = decodeMatrix();
					morphFillStyle.endGradientMatrix = decodeMatrix();
					morphFillStyle.gradient = decodeMorphGradient();
					break;
				case MorphFillStyleType.REPEATING_BITMAP_FILL:
				case MorphFillStyleType.CLIPPED_BITMAP_FILL:
				case MorphFillStyleType.NON_SMOOTHED_REPEATING_BITMAP_FILL:
				case MorphFillStyleType.NON_SMOOTHED_CLIPPED_BITMAP_FILL:
					morphFillStyle.bitmapId = _in.readUI16();
					morphFillStyle.startBitmapMatrix = decodeMatrix();
					morphFillStyle.endBitmapMatrix = decodeMatrix();
					break;
			}

			return morphFillStyle;
		}

		private function decodeMorphFillStyleArray():Array
		{
			var fillStyles:Array = [];
			var count:uint = _in.readUI8();
			if (count === 0xff)
			{
				count = _in.readUI16();
			}

			for (var i:int = 0; i < count; i++)
			{
				fillStyles[i] = decodeMorphFillStyle();
			}

			return fillStyles;
		}

		private function decodeMorphGradient():Array
		{
			var numGradients:uint = _in.readUI8();
			var records:Array = [];
			for (var i:int = 0; i < numGradients; i++)
			{
				records[i] = decodeMorphGradientRecord();
			}

			return records;
		}

		private function decodeMorphGradientRecord():MorphGradientRecord
		{
			var morphGradientRecord:MorphGradientRecord = new MorphGradientRecord();
			morphGradientRecord.startRatio = _in.readUI8();
			morphGradientRecord.startColor = decodeRGBA();
			morphGradientRecord.endRatio = _in.readUI8();
			morphGradientRecord.endColor = decodeRGBA();

			return morphGradientRecord;
		}

		private function decodeMorphLineStyle():MorphLineStyle
		{
			var morphLineStyle:MorphLineStyle = new MorphLineStyle();
			morphLineStyle.startWidth = _in.readUI16();
			morphLineStyle.endWidth = _in.readUI16();
			morphLineStyle.startColor = decodeRGBA();
			morphLineStyle.endColor = decodeRGBA();

			return morphLineStyle;
		}

		private function decodeMorphLineStyle2():MorphLineStyle2
		{
			var morphLineStyle2:MorphLineStyle2 = new MorphLineStyle2();
			morphLineStyle2.startWidth = _in.readUI16();
			morphLineStyle2.endWidth = _in.readUI16();
			morphLineStyle2.startCapStyle = _in.readUB(2);
			morphLineStyle2.joinStyle = _in.readUB(2);
			morphLineStyle2.hasFill = _in.readFLAG();
			morphLineStyle2.noHScale = _in.readFLAG();
			morphLineStyle2.noVScale = _in.readFLAG();
			morphLineStyle2.pixelHinting = _in.readFLAG();
			_in.readUB(5);
			morphLineStyle2.noClose = _in.readFLAG();
			morphLineStyle2.endCapStyle = _in.readUB(2);

			if (morphLineStyle2.joinStyle === JoinStyle.JOIN_STYLE_MITER)
			{
				morphLineStyle2.miterLimitFactor = _in.readUI16();
			}
			if (!morphLineStyle2.hasFill)
			{
				morphLineStyle2.startColor = decodeRGBA();
				morphLineStyle2.endColor = decodeRGBA();
			}
			else
			{
				morphLineStyle2.fillType = decodeMorphFillStyle();
			}

			return morphLineStyle2;
		}

		private function decodeMorphLineStyleArray():Array
		{
			var lineStyles:Array = [];
			var i:int, count:uint = _in.readUI8();
			if (count === 0xff)
			{
				count = _in.readUI16();
			}

			if (currentTag === Tag.DEFINE_MORPH_SHAPE)
			{
				for (i = 0; i < count; i++)
				{
					lineStyles[i] = decodeMorphLineStyle();
				}
			}
			else
			{
				for (i = 0; i < count; i++)
				{
					lineStyles[i] = decodeMorphLineStyle2();
				}
			}

			return lineStyles;
		}

		private function decodeRGB():RGB
		{
			var rgb:RGB = new RGB();
			rgb.red = _in.readUI8();
			rgb.green = _in.readUI8();
			rgb.blue = _in.readUI8();

			return rgb;
		}

		private function decodeRGBA():RGBA
		{
			var rgba:RGBA = new RGBA();
			rgba.red = _in.readUI8();
			rgba.green = _in.readUI8();
			rgba.blue = _in.readUI8();
			rgba.alpha = _in.readUI8();

			return rgba;
		}

		private function decodeRect():Rect
		{
			_in.resetCursor();

			var rect:Rect = new Rect();
			var len:uint = _in.readUB(5);
			rect.minBits = len;
			rect.xMin = _in.readSB(len);
			rect.xMax = _in.readSB(len);
			rect.yMin = _in.readSB(len);
			rect.yMax = _in.readSB(len);

			return rect;
		}

		private function decodeShape():Shape
		{
			var shape:Shape = new Shape();
			_in.resetCursor();
			shape.fillMinBits = _in.readUB(4);
			shape.lineMinBits = _in.readUB(4);
			shape.shapeRecords = decodeShapeRecords(shape.fillMinBits, shape.lineMinBits);

			return shape;
		}

		private function decodeShapeRecords(numFillBits:uint, numLineBits:uint):Array
		{
			var records:Array = [];
			var idx:uint = 0;
			var len:uint;

			while (true)
			{
				var flags:uint = _in.readUB(6);

				if ((flags & 32) === 32 && (flags & 16) === 16)
				{
					var straightEdgeRecord:StraightEdgeRecord = new StraightEdgeRecord();
					len = (flags & 0xf) + 2;
					straightEdgeRecord.minBits = len;

					var generalLine:Boolean = _in.readFLAG();
					var vertLine:Boolean = false;
					if (!generalLine)
					{
						vertLine = _in.readFLAG();
					}
					straightEdgeRecord.generalLine = generalLine;
					straightEdgeRecord.vertLine = vertLine;
					if (generalLine || !vertLine)
					{
						straightEdgeRecord.deltaX = _in.readSB(len);
					}
					if (generalLine || vertLine)
					{
						straightEdgeRecord.deltaY = _in.readSB(len);
					}

					records[idx++] = straightEdgeRecord;
				}
				else if ((flags & 32) === 32)
				{
					var curvedEdgeRecord:CurvedEdgeRecord = new CurvedEdgeRecord();
					len = (flags & 0xf) + 2;
					curvedEdgeRecord.minBits = len;

					curvedEdgeRecord.controlDeltaX = _in.readSB(len);
					curvedEdgeRecord.controlDeltaY = _in.readSB(len);
					curvedEdgeRecord.anchorDeltaX = _in.readSB(len);
					curvedEdgeRecord.anchorDeltaY = _in.readSB(len);

					records[idx++] = curvedEdgeRecord;
				}
				else if ((flags & 0x1f) > 0)
				{
					var styleChangeRecord:StyleChangeRecord = new StyleChangeRecord();
					if ((flags & 1) === 1)
					{
						len = _in.readUB(5);
						styleChangeRecord.moveMinBits = len;
						styleChangeRecord.stateMoveTo = true;
						styleChangeRecord.moveDeltaX = _in.readSB(len);
						styleChangeRecord.moveDeltaY = _in.readSB(len);
					}
					if ((flags & 2) === 2)
					{
						styleChangeRecord.stateFillStyle0 = true;
						styleChangeRecord.fillStyle0 = _in.readUB(numFillBits);
					}
					if ((flags & 4) === 4)
					{
						styleChangeRecord.stateFillStyle1 = true;
						styleChangeRecord.fillStyle1 = _in.readUB(numFillBits);
					}
					if ((flags & 8) === 8)
					{
						styleChangeRecord.stateLineStyle = true;
						styleChangeRecord.lineStyle = _in.readUB(numLineBits);
					}
					if ((flags & 16) === 16)
					{
						styleChangeRecord.stateNewStyles = true;
						styleChangeRecord.fillStyles = decodeFillStyleArray();
						styleChangeRecord.lineStyles = decodeLineStyleArray();
						numFillBits = _in.readUB(4);
						numLineBits = _in.readUB(4);
						styleChangeRecord.fillMinBits = numFillBits;
						styleChangeRecord.lineMinBits = numLineBits;
					}

					records[idx++] = styleChangeRecord;
				}
				else
				{
					records[idx++] = new EndShapeRecord();
					break;
				}
			}

			return records;
		}

		private function decodeShapeWithStyle():ShapeWithStyle
		{
			var shapeWithStyle:ShapeWithStyle = new ShapeWithStyle();
			shapeWithStyle.fillStyles = decodeFillStyleArray();
			shapeWithStyle.lineStyles = decodeLineStyleArray();
			_in.resetCursor();
			shapeWithStyle.fillMinBits = _in.readUB(4);
			shapeWithStyle.lineMinBits = _in.readUB(4);
			shapeWithStyle.shapeRecords = decodeShapeRecords(shapeWithStyle.fillMinBits, shapeWithStyle.lineMinBits);

			return shapeWithStyle;
		}

		private function decodeSoundInfo():SoundInfo
		{
			_in.resetCursor();
			var soundInfo:SoundInfo = new SoundInfo();
			_in.readUB(2);
			soundInfo.syncStop = _in.readFLAG();
			soundInfo.syncNoMultiple = _in.readFLAG();
			soundInfo.hasEnvelope = _in.readFLAG();
			soundInfo.hasLoops = _in.readFLAG();
			soundInfo.hasOutPoint = _in.readFLAG();
			soundInfo.hasInPoint = _in.readFLAG();

			if (soundInfo.hasInPoint)
			{
				soundInfo.inPoint = _in.readUI32();
			}
			if (soundInfo.hasOutPoint)
			{
				soundInfo.outPoint = _in.readUI32();
			}
			if (soundInfo.hasLoops)
			{
				soundInfo.loopCount = _in.readUI16();
			}
			if (soundInfo.hasEnvelope)
			{
				var data:Array = soundInfo.envelopeRecords = [];
				var len:uint = _in.readUI8();
				for (var i:int = 0; i < len; i++)
				{
					var soundEnvelope:SoundEnvelope = new SoundEnvelope();
					soundEnvelope.pos44 = _in.readUI32();
					soundEnvelope.leftLevel = _in.readUI16();
					soundEnvelope.rightLevel = _in.readUI16();

					data[i] = soundEnvelope;
				}
			}

			return soundInfo;
		}

		private function decodeString(length:uint = 0):String
		{
			if (_version <= SWFVersion.VERSION_5)
			{
				return _in.readString(length, "shift-jis");
			}

			return _in.readString(length);
		}

		private function decodeTag(filter:Function):ITag
		{
			var position:uint = _in.position;
			var codeAndLength:uint = _in.readUI16();
			var code:uint = codeAndLength >> 6;
			var length:uint = codeAndLength & 0x3f;
			var longHeader:Boolean = false;
			if (length === 0x3f)
			{
				length = _in.readSI32();
				longHeader = true;
			}

			if (filter !== null && !filter(code))
			{
				_in.skip(length);
				return _skippedTag;
			}

			_tagStack.push(code);

			var readTagFunction:Function = lookupReadTagFunction(code);
			var tag:ITag;
			var bytes:ByteArray = new ByteArray();

			bytes.endian = Endian.LITTLE_ENDIAN;
			if (length > 0)
			{
				try
				{
					_in.getBytes().readBytes(bytes, 0, length);
				}
				catch (e:Error)
				{
					notifyError(_msg.format("000", e.message));
					return null;
				}
			}
			_in.pushBytes(bytes);

			try
			{
				if (readTagFunction === decodeTagUnknown)
				{
					tag = readTagFunction(code, length);
				}
				else
				{
					tag = readTagFunction(length);
				}
			}
			catch (e:Error)
			{
				notifyError(_msg.format("000", e.message));
			}
			finally
			{
				_in.popBytes();
				if (bytes.position !== bytes.length)
				{
					notifyError(_msg.format("005", Tag.getTagNameByCode(code)));
				}
			}

			if (tag === null)
			{
				return null;
			}

			_tagStack.pop();

			if (longHeader)
			{
				tag.longHeader = true;
			}
			tag.length = length;
			tag.position = position;

			return tag;
		}

		private function decodeTagCSMTextSettings(length:uint):ITag
		{
			var tag:CSMTextSettings = new CSMTextSettings();
			tag.textId = _in.readUI16();
			tag.useFlashType = _in.readUB(2);
			tag.gridFit = _in.readUB(3);
			_in.readUB(3);
			tag.thickness = _in.readFLOAT32();
			tag.sharpness = _in.readFLOAT32();
			_in.readUI8();

			return tag;
		}

		private function decodeTagDebugID(length:uint):ITag
		{
			var tag:DebugID = new DebugID();
			tag.uuid = _in.readBytes(length);

			return tag;
		}

		private function decodeTagDefineBinaryData(length:uint):ITag
		{
			var tag:DefineBinaryData = new DefineBinaryData();
			tag.tag = _in.readUI16();
			_in.readUI32();
			tag.data = _in.readBytes(length - 6);

			return tag;
		}

		private function decodeTagDefineBits(length:uint):ITag
		{
			var tag:DefineBits = new DefineBits();
			tag.characterId = _in.readUI16();
			tag.jpegData = _in.readBytes(length - 2);

			return tag;
		}

		private function decodeTagDefineBitsJPEG2(length:uint):ITag
		{
			var tag:DefineBitsJPEG2 = new DefineBitsJPEG2();
			tag.characterId = _in.readUI16();
			tag.imageData = _in.readBytes(length - 2);

			return tag;
		}

		private function decodeTagDefineBitsJPEG3(length:uint):ITag
		{
			var tag:DefineBitsJPEG3 = new DefineBitsJPEG3();
			tag.characterId = _in.readUI16();
			var offset:uint = _in.readUI32();
			tag.imageData = _in.readBytes(offset);
			tag.bitmapAlphaData = _in.readBytes(length - offset - 6);

			return tag;
		}

		private function decodeTagDefineBitsJPEG4(length:uint):ITag
		{
			var tag:DefineBitsJPEG4 = new DefineBitsJPEG4();
			tag.characterId = _in.readUI16();
			var offset:uint = _in.readUI32();
			tag.deblockingParam = _in.readUI16();
			tag.imageData = _in.readBytes(offset);
			tag.bitmapAlphaData = _in.readBytes(length - offset - 8);

			return tag;
		}

		private function decodeTagDefineBitsLossless(length:uint):ITag
		{
			var tag:DefineBitsLossless = new DefineBitsLossless();
			tag.characterId = _in.readUI16();
			var format:uint = tag.format = _in.readUI8();
			tag.bitmapWidth = _in.readUI16();
			tag.bitmapHeight = _in.readUI16();

			var bitmapColorTableSize:uint = 0;
			if (format === BitmapType.COLOR_MAP_DATA)
			{
				length--;
				bitmapColorTableSize = _in.readUI8() + 1;
			}

			var bytes:ByteArray = _in.readBytes(length - 7);
			bytes.uncompress();
			_in.pushBytes(bytes);

			var imageData:ImageData;
			switch (format)
			{
				case BitmapType.COLOR_MAP_DATA:
					var colorMapData:ColorMapData = new ColorMapData();
					var data:Array = colorMapData.colorTableRGB = [];
					for (var i:int = 0; i < bitmapColorTableSize; i++)
					{
						data[i] = decodeRGB();
					}

					var width:int = tag.bitmapWidth;
					if (width % 4)
					{
						width = (int(width / 4) + 1) * 4;
					}
					colorMapData.colormapPixelData = _in.readBytes(width * tag.bitmapHeight);
					imageData = colorMapData;

					break;
				case BitmapType.BITMAP_DATA_PIX_15:
					var bitmapData15:BitmapData15 = new BitmapData15();
					var len:int = tag.bitmapWidth;
					if (len % 2)
					{
						len += 1;
					}
					len *= tag.bitmapHeight * 2;
					bitmapData15.bitmapPixelData = _in.readBytes(len);
					imageData = bitmapData15;

					break;
				case BitmapType.BITMAP_DATA_PIX_24:
					var bitmapData24:BitmapData24 = new BitmapData24();
					bitmapData24.bitmapPixelData = _in.readBytes(tag.bitmapWidth * tag.bitmapHeight * 4);
					imageData = bitmapData24;

					break;
			}
			tag.zlibBitmapData = imageData;
			_in.popBytes();

			return tag;
		}

		private function decodeTagDefineBitsLossless2(length:uint):ITag
		{
			var tag:DefineBitsLossless2 = new DefineBitsLossless2();
			tag.characterId = _in.readUI16();
			var format:uint = tag.format = _in.readUI8();
			tag.bitmapWidth = _in.readUI16();
			tag.bitmapHeight = _in.readUI16();

			var bitmapColorTableSize:uint = 0;
			if (format === AlphaBitmapType.ALPHA_COLOR_MAP_DATA)
			{
				length--;
				bitmapColorTableSize = _in.readUI8() + 1;
			}

			var bytes:ByteArray = _in.readBytes(length - 7);
			bytes.uncompress();
			_in.pushBytes(bytes);

			var imageData:AlphaImageData;
			var bitmapData:AlphaBitmapData;
			switch (format)
			{
				case AlphaBitmapType.ALPHA_COLOR_MAP_DATA:
					var colorMapData:AlphaColorMapData = new AlphaColorMapData();
					var data:Array = colorMapData.colorTableRGBA = [];
					for (var i:int = 0; i < bitmapColorTableSize; i++)
					{
						data[i] = decodeRGBA();
					}

					var width:int = tag.bitmapWidth;
					if (width % 4)
					{
						width = (int(width / 4) + 1) * 4;
					}
					colorMapData.colormapPixelData = _in.readBytes(width * tag.bitmapHeight);
					imageData = colorMapData;

					break;
				case AlphaBitmapType.ALPHA_BITMAP_DATA_4:
				case AlphaBitmapType.ALPHA_BITMAP_DATA_5:
					bitmapData = new AlphaBitmapData();
					bitmapData.bitmapPixelData = _in.readBytes(tag.bitmapWidth * tag.bitmapHeight * 4);
					imageData = bitmapData;

					break;
			}
			tag.zlibBitmapData = imageData;
			_in.popBytes();

			return tag;
		}

		private function decodeTagDefineButton(length:uint):ITag
		{
			var tag:DefineButton = new DefineButton();
			tag.buttonId = _in.readUI16();
			_in.pushPosition();
			tag.characters = decodeButtonRecords();
			tag.actions = decodeActionRecords(length - 3 - _in.popPosition(false));
			_in.readUI8();

			return tag;
		}

		private function decodeTagDefineButton2(length:uint):ITag
		{
			var end:uint = _in.position + length;
			var tag:DefineButton2 = new DefineButton2();
			tag.buttonId = _in.readUI16();
			_in.readUB(7);
			tag.trackAsMenu = _in.readFLAG();
			var offset:uint = _in.readUI16();
			tag.characters = decodeButtonRecords();
			var data:Array = tag.actions = [];
			var idx:int = 0;

			if (offset > 0)
			{
				while (true)
				{
					var size:uint = _in.readUI16();
					var action:ButtonCondAction = new ButtonCondAction();
					data[idx++] = action;

					action.condIdleToOverDown = _in.readFLAG();
					action.condOutDownToIdle = _in.readFLAG();
					action.condOutDownToOverDown = _in.readFLAG();
					action.condOverDownToOutDown = _in.readFLAG();
					action.condOverDownToOverUp = _in.readFLAG();
					action.condOverUpToOverDown = _in.readFLAG();
					action.condOverUpToIdle = _in.readFLAG();
					action.condIdleToOverUp = _in.readFLAG();
					action.condKeyPress = _in.readUB(7);
					action.condOverDownToIdle = _in.readFLAG();
					action.actions = decodeActionRecords(size === 0 ? (end - _in.position) - 1 : (size - 5));
					_in.skip(1);

					if (size === 0)
					{
						break;
					}
				}
			}

			return tag;
		}

		private function decodeTagDefineButtonCxform(length:uint):ITag
		{
			var tag:DefineButtonCxform = new DefineButtonCxform();
			tag.buttonId = _in.readUI16();
			tag.buttonColorTransform = decodeCXForm();

			return tag;
		}

		private function decodeTagDefineButtonSound(length:uint):ITag
		{
			var tag:DefineButtonSound = new DefineButtonSound();
			tag.buttonId = _in.readUI16();
			tag.buttonSoundChar0 = _in.readUI16();
			if (tag.buttonSoundChar0 !== 0)
			{
				tag.buttonSoundInfo0 = decodeSoundInfo();
			}
			tag.buttonSoundChar1 = _in.readUI16();
			if (tag.buttonSoundChar1 !== 0)
			{
				tag.buttonSoundInfo1 = decodeSoundInfo();
			}
			tag.buttonSoundChar2 = _in.readUI16();
			if (tag.buttonSoundChar2 !== 0)
			{
				tag.buttonSoundInfo2 = decodeSoundInfo();
			}
			tag.buttonSoundChar3 = _in.readUI16();
			if (tag.buttonSoundChar3 !== 0)
			{
				tag.buttonSoundInfo3 = decodeSoundInfo();
			}

			return tag;
		}

		private function decodeTagDefineEditText(length:uint):ITag
		{
			var tag:DefineEditText = new DefineEditText();
			tag.characterId = _in.readUI16();
			tag.bounds = decodeRect();
			_in.resetCursor();
			tag.hasText = _in.readFLAG();
			tag.wordWrap = _in.readFLAG();
			tag.multiline = _in.readFLAG();
			tag.password = _in.readFLAG();
			tag.readOnly = _in.readFLAG();
			tag.hasTextColor = _in.readFLAG();
			tag.hasMaxLength = _in.readFLAG();
			tag.hasFont = _in.readFLAG();
			tag.hasFontClass = _in.readFLAG();
			tag.autoSize = _in.readFLAG();
			tag.hasLayout = _in.readFLAG();
			tag.noSelect = _in.readFLAG();
			tag.border = _in.readFLAG();
			tag.wasStatic = _in.readFLAG();
			tag.html = _in.readFLAG();
			tag.useOutlines = _in.readFLAG();

			if (tag.hasFont)
			{
				tag.fontId = _in.readUI16();
			}
			if (tag.hasFontClass)
			{
				tag.fontClass = decodeString();
			}
			if (tag.hasFont)
			{
				tag.fontHeight = _in.readUI16();
			}
			if (tag.hasTextColor)
			{
				tag.textColor = decodeRGBA();
			}
			if (tag.hasMaxLength)
			{
				tag.maxLength = _in.readUI16();
			}
			if (tag.hasLayout)
			{
				tag.align = _in.readUI8();
				tag.leftMargin = _in.readUI16();
				tag.rightMargin = _in.readUI16();
				tag.indent = _in.readUI16();
				tag.leading = _in.readSI16();
			}
			tag.variableName = decodeString();
			if (tag.hasText)
			{
				tag.initialText = decodeString();
			}

			return tag;
		}

		private function decodeTagDefineFont(length:uint):ITag
		{
			var tag:DefineFont = new DefineFont();
			tag.fontId = _in.readUI16();

			var firstOffset:uint = _in.readUI16();
			var numGlyphs:uint = tag.numGlyphs = firstOffset / 2;
			var offsetTable:Array = [firstOffset];
			var glyphShapeTable:Array = [];
			var i:int;

			for (i = 1; i < numGlyphs; i++)
			{
				offsetTable[i] = _in.readUI16();
			}
			for (i = 0; i < numGlyphs; i++)
			{
				glyphShapeTable[i] = decodeShape();
			}
			tag.offsetTable = offsetTable;
			tag.glyphShapeTable = glyphShapeTable;

			return tag;
		}

		private function decodeTagDefineFont2(length:uint):ITag
		{
			var tag:DefineFont2 = new DefineFont2();
			decodeDefineFont2OR3(length, tag);

			return tag;
		}

		private function decodeTagDefineFont3(length:uint):ITag
		{
			var tag:DefineFont3 = new DefineFont3();
			decodeDefineFont2OR3(length, tag);

			return tag;
		}

		private function decodeTagDefineFont4(length:uint):ITag
		{
			var tag:DefineFont4 = new DefineFont4();
			tag.fontId = _in.readUI16();
			_in.readUB(5);
			tag.hasFontData = _in.readFLAG();
			tag.italic = _in.readFLAG();
			tag.bold = _in.readFLAG();

			_in.pushPosition();
			tag.fontName = decodeString();
			var len:uint = length - _in.popPosition(false) - 3;

			if (tag.hasFontData)
			{
				tag.fontData = _in.readBytes(len);
			}

			return tag;
		}

		private function decodeTagDefineFontAlignZones(length:uint):ITag
		{
			var tag:DefineFontAlignZones = new DefineFontAlignZones();
			tag.fontId = _in.readUI16();
			tag.cmsTableHint = _in.readUB(2);
			_in.readUB(6);

			var data:Array = tag.zoneTable = [];
			var total:uint = 3, idx:int = 0;
			while (true)
			{
				if (length <= total)
				{
					break;
				}

				var numZoneData:uint = _in.readUI8();
				var zoneRecord:ZoneRecord = new ZoneRecord();
				data[idx++] = zoneRecord;
				var zoneData:Array = zoneRecord.zoneData = [];

				for (var i:int = 0; i < numZoneData; i++)
				{
					var zoneDataEntry:ZoneData = new ZoneData();
					zoneDataEntry.alignmentCoordinate = _in.readFLOAT16();
					zoneDataEntry.range = _in.readFLOAT16();
					zoneData[i] = zoneDataEntry;
				}

				_in.readUB(6);
				zoneRecord.zoneMaskY = _in.readFLAG();
				zoneRecord.zoneMaskX = _in.readFLAG();

				total += 2 + numZoneData * 4;
			}

			return tag;
		}

		private function decodeTagDefineFontInfo(length:uint):ITag
		{
			var tag:DefineFontInfo = new DefineFontInfo();
			tag.fontId = _in.readUI16();
			var len:uint = _in.readUI8();
			tag.fontName = decodeString(len);
			_in.readUB(2);
			tag.smallText = _in.readFLAG();
			tag.shiftJIS = _in.readFLAG();
			tag.ansi = _in.readFLAG();
			tag.italic = _in.readFLAG();
			tag.bold = _in.readFLAG();
			tag.wide = _in.readFLAG();

			var i:int;
			var codeTable:Array = tag.codeTable = [];
			length -= len + 4;
			if (tag.wide)
			{
				length /= 2;
				for (i = 0; i < length; i++)
				{
					codeTable[i] = _in.readUI16();
				}
			}
			else
			{
				for (i = 0; i < length; i++)
				{
					codeTable[i] = _in.readUI8();
				}
			}

			return tag;
		}

		private function decodeTagDefineFontInfo2(length:uint):ITag
		{
			var tag:DefineFontInfo2 = new DefineFontInfo2();
			tag.fontId = _in.readUI16();
			var len:uint = _in.readUI8();
			tag.fontName = decodeString(len);
			_in.readUB(2);
			tag.smallText = _in.readFLAG();
			tag.shiftJIS = _in.readFLAG();
			tag.ansi = _in.readFLAG();
			tag.italic = _in.readFLAG();
			tag.bold = _in.readFLAG();
			_in.readFLAG();

			_in.pushPosition();
			tag.languageCode = decodeLanguageCode();
			var codeTable:Array = tag.codeTable = [];
			length -= len + 4 + _in.popPosition(false);
			length /= 2;

			for (var i:int = 0; i < length; i++)
			{
				codeTable[i] = _in.readUI16();
			}

			return tag;
		}

		private function decodeTagDefineFontName(length:uint):ITag
		{
			var tag:DefineFontName = new DefineFontName();
			tag.fontId = _in.readUI16();
			tag.fontName = decodeString();
			tag.fontCopyright = decodeString();

			return tag;
		}

		private function decodeTagDefineMorphShape(length:uint):ITag
		{
			var tag:DefineMorphShape = new DefineMorphShape();
			tag.characterId = _in.readUI16();
			tag.startBounds = decodeRect();
			tag.endBounds = decodeRect();
			_in.readUI32();
			tag.morphFillStyles = decodeMorphFillStyleArray();
			tag.morphLineStyles = decodeMorphLineStyleArray();
			tag.startEdges = decodeShape();
			tag.endEdges = decodeShape();

			return tag;
		}

		private function decodeTagDefineMorphShape2(length:uint):ITag
		{
			var tag:DefineMorphShape2 = new DefineMorphShape2();
			tag.characterId = _in.readUI16();
			tag.startBounds = decodeRect();
			tag.endBounds = decodeRect();
			tag.startEdgeBounds = decodeRect();
			tag.endEdgeBounds = decodeRect();
			_in.readUB(6);
			tag.usesNonScalingStrokes = _in.readFLAG();
			tag.usesScalingStrokes = _in.readFLAG();
			_in.readUI32();
			tag.morphFillStyles = decodeMorphFillStyleArray();
			tag.morphLineStyles = decodeMorphLineStyleArray();
			tag.startEdges = decodeShape();
			tag.endEdges = decodeShape();

			return tag;
		}

		private function decodeTagDefineScalingGrid(length:uint):ITag
		{
			var tag:DefineScalingGrid = new DefineScalingGrid();
			tag.characterId = _in.readUI16();
			tag.splitter = decodeRect();

			return tag;
		}

		private function decodeTagDefineSceneAndFrameLabelData(length:uint):ITag
		{
			var tag:DefineSceneAndFrameLabelData = new DefineSceneAndFrameLabelData();
			var i:uint, len:uint, array:Array;

			len = _in.readEncodedU32();
			tag.sceneData = array = [];
			for (i = 0; i < len; i++)
			{
				var scene:SceneData = new SceneData();
				scene.frameOffset = _in.readEncodedU32();
				scene.name = decodeString();
				array[array.length] = scene;
			}

			len = _in.readEncodedU32();
			tag.frameLabelData = array = [];
			for (i = 0; i < len; i++)
			{
				var frame:FrameLabelData = new FrameLabelData();
				frame.frameNumber = _in.readEncodedU32();
				frame.label = decodeString();
				array[array.length] = frame;
			}

			return tag;
		}

		private function decodeTagDefineShape(length:uint):ITag
		{
			var tag:DefineShape = new DefineShape();
			tag.shapeId = _in.readUI16();
			tag.shapeBounds = decodeRect();
			tag.shapes = decodeShapeWithStyle();

			return tag;
		}

		private function decodeTagDefineShape2(length:uint):ITag
		{
			var tag:DefineShape2 = new DefineShape2();
			tag.shapeId = _in.readUI16();
			tag.shapeBounds = decodeRect();
			tag.shapes = decodeShapeWithStyle();

			return tag;
		}

		private function decodeTagDefineShape3(length:uint):ITag
		{
			var tag:DefineShape3 = new DefineShape3();
			tag.shapeId = _in.readUI16();
			tag.shapeBounds = decodeRect();
			tag.shapes = decodeShapeWithStyle();

			return tag;
		}

		private function decodeTagDefineShape4(length:uint):ITag
		{
			var tag:DefineShape4 = new DefineShape4();
			tag.shapeId = _in.readUI16();
			tag.shapeBounds = decodeRect();
			tag.edgeBounds = decodeRect();
			_in.resetCursor();
			_in.readUB(5);
			tag.usesFillWindingRule = _in.readFLAG();
			tag.usesNonScalingStrokes = _in.readFLAG();
			tag.usesScalingStrokes = _in.readFLAG();
			tag.shapes = decodeShapeWithStyle();

			return tag;
		}

		private function decodeTagDefineSound(length:uint):ITag
		{
			var tag:DefineSound = new DefineSound();
			tag.soundId = _in.readUI16();
			tag.soundFormat = _in.readUB(4);
			tag.soundRate = _in.readUB(2);
			tag.soundSize = _in.readUB(1);
			tag.soundType = _in.readUB(1);
			tag.soundSampleCount = _in.readUI32();
			tag.soundData = _in.readBytes(length - 7);

			return tag;
		}

		private function decodeTagDefineSprite(length:uint):ITag
		{
			var tag:DefineSprite = new DefineSprite();
			tag.spriteId = _in.readUI16();
			tag.frameCount = _in.readUI16();
			var tags:Array = tag.tags = [];
			var childTag:ITag;

			do
			{
				childTag = decodeTag(null);
				if (childTag === null)
				{
					return null;
				}

				tags[tags.length] = childTag;
			} while (childTag.code !== Tag.END);

			return tag;
		}

		private function decodeTagDefineText(length:uint):ITag
		{
			var tag:DefineText = new DefineText();
			decodeDefineText1Or2(length, tag);

			return tag;
		}

		private function decodeTagDefineText2(length:uint):ITag
		{
			var tag:DefineText2 = new DefineText2();
			decodeDefineText1Or2(length, tag);

			return tag;
		}

		private function decodeTagDefineVideoStream(length:uint):ITag
		{
			var tag:DefineVideoStream = new DefineVideoStream();
			tag.characterId = _in.readUI16();
			tag.numFrames = _in.readUI16();
			tag.width = _in.readUI16();
			tag.height = _in.readUI16();
			_in.readUB(4);
			tag.deblocking = _in.readUB(3);
			tag.smoothing = _in.readFLAG();
			tag.codecId = _in.readUI8();

			return tag;
		}

		private function decodeTagDoABC(length:uint):ITag
		{
			var tag:DoABC = new DoABC();
			tag.abcData = new ABCData();
			tag.abcData.data = _in.readBytes(length);

			return tag;
		}

		private function decodeTagDoABC2(length:uint):ITag
		{
			var tag:DoABC2 = new DoABC2();
			tag.flags = _in.readUI32();
			_in.pushPosition();
			tag.name = decodeString();
			tag.abcData = new ABCData();
			tag.abcData.data = _in.readBytes(length - 4 - _in.popPosition(false));

			return tag;
		}

		private function decodeTagDoAction(length:uint):ITag
		{
			var tag:DoAction = new DoAction();
			tag.actions = decodeActionRecords(length - 1);
			_in.readUI8();

			return tag;
		}

		private function decodeTagDoInitAction(length:uint):ITag
		{
			var tag:DoInitAction = new DoInitAction();
			tag.spriteId = _in.readUI16();
			tag.actions = decodeActionRecords(length - 3);
			_in.readUI8();

			return tag;
		}

		private function decodeTagEnableDebugger(length:uint):ITag
		{
			var tag:EnableDebugger = new EnableDebugger();
			tag.password = _in.readBytes(length);

			return tag;
		}

		private function decodeTagEnableDebugger2(length:uint):ITag
		{
			var tag:EnableDebugger2 = new EnableDebugger2();
			tag.reserved = _in.readUI16();
			tag.password = _in.readBytes(length - 2);

			return tag;
		}

		private function decodeTagEnd(length:uint):ITag
		{
			return new End();
		}

		private function decodeTagExportAssets(length:uint):ITag
		{
			var tag:ExportAssets = new ExportAssets();
			var len:int = _in.readUI16();
			var assets:Array = [];

			for (var i:int = 0; i < len; i++)
			{
				var asset:Asset = new Asset();
				asset.tag = _in.readUI16();
				asset.name = decodeString();
				assets[assets.length] = asset;
			}
			tag.assets = assets;

			return tag;
		}

		private function decodeTagFileAttributes(length:uint):ITag
		{
			var tag:FileAttributes = new FileAttributes();
			_in.readUB(1);
			tag.useDirectBlit = _in.readFLAG();
			tag.useGPU = _in.readFLAG();
			tag.hasMetadata = _in.readFLAG();
			tag.actionScript3 = _in.readFLAG();
			tag.suppressCrossDomainCaching = _in.readFLAG();
			tag.swfRelativeUrls = _in.readFLAG();
			tag.useNetwork = _in.readFLAG();
			_in.readUB(24);

			return tag;
		}

		private function decodeTagFrameLabel(length:uint):ITag
		{
			var tag:FrameLabel = new FrameLabel();
			_in.pushPosition();
			tag.name = decodeString();

			if (_version >= SWFVersion.VERSION_6)
			{
				if (length > _in.popPosition(false))
				{
					tag.namedAnchor = _in.readUI8() > 0;
				}
			}
			else
			{
				_in.popPosition(false);
			}

			return tag;
		}

		private function decodeTagImportAssets(length:uint):ITag
		{
			var tag:ImportAssets = new ImportAssets();
			tag.url = decodeString();
			var len:int = _in.readUI16();
			var assets:Array = [];

			for (var i:int = 0; i < len; i++)
			{
				var asset:Asset = new Asset();
				asset.tag = _in.readUI16();
				asset.name = decodeString();
				assets[assets.length] = asset;
			}
			tag.assets = assets;

			return tag;
		}

		private function decodeTagImportAssets2(length:uint):ITag
		{
			var tag:ImportAssets2 = new ImportAssets2();
			tag.url = decodeString();
			_in.readUI16();
			var len:int = _in.readUI16();
			var assets:Array = [];

			for (var i:int = 0; i < len; i++)
			{
				var asset:Asset = new Asset();
				asset.tag = _in.readUI16();
				asset.name = decodeString();
				assets[assets.length] = asset;
			}

			return tag;
		}

		private function decodeTagJPEGTables(length:uint):ITag
		{
			var tag:JPEGTables = new JPEGTables();
			tag.jpegData = _in.readBytes(length);

			return tag;
		}

		private function decodeTagMetadata(length:uint):ITag
		{
			var tag:Metadata = new Metadata();
			tag.metadata = decodeString();

			return tag;
		}

		private function decodeTagPlaceObject(length:uint):ITag
		{
			var tag:PlaceObject = new PlaceObject();

			_in.pushPosition();

			tag.characterId = _in.readUI16();
			tag.depth = _in.readUI16();
			tag.matrix = decodeMatrix();

			if (length > _in.popPosition(false))
			{
				tag.colorTransform = decodeCXForm();
			}

			return tag;
		}

		private function decodeTagPlaceObject2(length:uint):ITag
		{
			var tag:PlaceObject2 = new PlaceObject2();
			tag.hasClipAction = _in.readFLAG();
			tag.hasClipDepth = _in.readFLAG();
			tag.hasName = _in.readFLAG();
			tag.hasRatio = _in.readFLAG();
			tag.hasColorTransform = _in.readFLAG();
			tag.hasMatrix = _in.readFLAG();
			tag.hasCharacter = _in.readFLAG();
			tag.hasMove = _in.readFLAG();

			tag.depth = _in.readUI16();
			if (tag.hasCharacter)
			{
				tag.characterId = _in.readUI16();
			}
			if (tag.hasMatrix)
			{
				tag.matrix = decodeMatrix();
			}
			if (tag.hasColorTransform)
			{
				tag.colorTransform = decodeCXFormWithAlpha();
			}
			if (tag.hasRatio)
			{
				tag.ratio = _in.readUI16();
			}
			if (tag.hasName)
			{
				tag.name = decodeString();
			}
			if (tag.hasClipDepth)
			{
				tag.clipDepth = _in.readUI16();
			}
			if (tag.hasClipAction)
			{
				tag.clipActions = decodeClipActions();
			}

			return tag;
		}

		private function decodeTagPlaceObject3(length:uint):ITag
		{
			var tag:PlaceObject3 = new PlaceObject3();
			tag.hasClipAction = _in.readFLAG();
			tag.hasClipDepth = _in.readFLAG();
			tag.hasName = _in.readFLAG();
			tag.hasRatio = _in.readFLAG();
			tag.hasColorTransform = _in.readFLAG();
			tag.hasMatrix = _in.readFLAG();
			tag.hasCharacter = _in.readFLAG();
			tag.hasMove = _in.readFLAG();
			_in.readUB(3);
			tag.hasImage = _in.readFLAG();
			tag.hasClassName = _in.readFLAG();
			tag.hasCacheAsBitmap = _in.readFLAG();
			tag.hasBlendMode = _in.readFLAG();
			tag.hasFilterList = _in.readFLAG();

			tag.depth = _in.readUI16();
			if (tag.hasClassName || (tag.hasImage && tag.hasCharacter))
			{
				tag.className = decodeString();
			}
			if (tag.hasCharacter)
			{
				tag.characterId = _in.readUI16();
			}
			if (tag.hasMatrix)
			{
				tag.matrix = decodeMatrix();
			}
			if (tag.hasColorTransform)
			{
				tag.colorTransform = decodeCXFormWithAlpha();
			}
			if (tag.hasRatio)
			{
				tag.ratio = _in.readUI16();
			}
			if (tag.hasName)
			{
				tag.name = decodeString();
			}
			if (tag.hasClipDepth)
			{
				tag.clipDepth = _in.readUI16();
			}
			if (tag.hasFilterList)
			{
				tag.surfaceFilterList = decodeFilterList();
			}
			if (tag.hasBlendMode)
			{
				tag.blendMode = _in.readUI8();
			}
			if (tag.hasCacheAsBitmap)
			{
				tag.bitmapCache = _in.readUI8();
			}
			if (tag.hasClipAction)
			{
				tag.clipActions = decodeClipActions();
			}

			return tag;
		}

		private function decodeTagProductInfo(length:uint):ITag
		{
			var tag:ProductInfo = new ProductInfo();
			tag.product = _in.readSI32();
			tag.edition = _in.readSI32();
			tag.majorVersion = _in.readSI8();
			tag.minorVersion = _in.readSI8();
			tag.build = _in.readSI64();
			tag.compileDate = _in.readSI64();

			return tag;
		}

		private function decodeTagProtect(length:uint):ITag
		{
			var tag:Protect = new Protect();
			tag.password = _in.readBytes(length);

			return tag;
		}

		private function decodeTagRemoveObject(length:uint):ITag
		{
			var tag:RemoveObject = new RemoveObject();
			tag.characterId = _in.readUI16();
			tag.depth = _in.readUI16();

			return tag;
		}

		private function decodeTagRemoveObject2(length:uint):ITag
		{
			var tag:RemoveObject2 = new RemoveObject2();
			tag.depth = _in.readUI16();

			return tag;
		}

		private function decodeTagScriptLimits(length:uint):ITag
		{
			var tag:ScriptLimits = new ScriptLimits();
			tag.maxRecursionDepth = _in.readUI16();
			tag.scriptTimeoutSeconds = _in.readUI16();

			return tag;
		}

		private function decodeTagSetBackgroundColor(length:uint):ITag
		{
			var tag:SetBackgroundColor = new SetBackgroundColor();
			tag.backgroundColor = decodeRGB();

			return tag;
		}

		private function decodeTagSetTabIndex(length:uint):ITag
		{
			var tag:SetTabIndex = new SetTabIndex();
			tag.depth = _in.readUI16();
			tag.tabIndex = _in.readUI16();

			return tag;
		}

		private function decodeTagShowFrame(length:uint):ITag
		{
			return new ShowFrame();
		}

		private function decodeTagSoundStreamBlock(length:uint):ITag
		{
			var tag:SoundStreamBlock = new SoundStreamBlock();
			tag.streamSoundData = _in.readBytes(length);

			return tag;
		}

		private function decodeTagSoundStreamHead(length:uint):ITag
		{
			var tag:SoundStreamHead = new SoundStreamHead();
			tag.reserved = _in.readUB(4);
			tag.playbackSoundRate = _in.readUB(2);
			tag.playbackSoundSize = _in.readUB(1);
			tag.playbackSoundType = _in.readUB(1);
			tag.streamSoundCompression = _in.readUB(4);
			tag.streamSoundRate = _in.readUB(2);
			tag.streamSoundSize = _in.readUB(1);
			tag.streamSoundType = _in.readUB(1);
			tag.streamSoundSampleCount = _in.readUI16();
			if (tag.streamSoundCompression === SoundFormat.MP3 && length > 4)
			{
				tag.latencySeek = _in.readSI16();
			}

			return tag;
		}

		private function decodeTagSoundStreamHead2(length:uint):ITag
		{
			var tag:SoundStreamHead2 = new SoundStreamHead2();
			tag.reserved = _in.readUB(4);
			tag.playbackSoundRate = _in.readUB(2);
			tag.playbackSoundSize = _in.readUB(1);
			tag.playbackSoundType = _in.readUB(1);
			tag.streamSoundCompression = _in.readUB(4);
			tag.streamSoundRate = _in.readUB(2);
			tag.streamSoundSize = _in.readUB(1);
			tag.streamSoundType = _in.readUB(1);
			tag.streamSoundSampleCount = _in.readUI16();
			if (tag.streamSoundCompression === SoundFormat.MP3 && length > 4)
			{
				tag.latencySeek = _in.readSI16();
			}

			return tag;
		}

		private function decodeTagStartSound(length:uint):ITag
		{
			var tag:StartSound = new StartSound();
			tag.soundId = _in.readUI16();
			tag.soundInfo = decodeSoundInfo();

			return tag;
		}

		private function decodeTagStartSound2(length:uint):ITag
		{
			var tag:StartSound2 = new StartSound2();
			tag.soundClassName = decodeString();
			tag.soundInfo = decodeSoundInfo();

			return tag;
		}

		private function decodeTagSymbolClass(length:uint):ITag
		{
			var tag:SymbolClass = new SymbolClass();
			var len:int = _in.readUI16();
			var symbols:Array = [];

			for (var i:int = 0; i < len; i++)
			{
				var symbol:Symbol = new Symbol();
				symbol.tag = _in.readUI16();
				symbol.name = decodeString();
				symbols[symbols.length] = symbol;
			}
			tag.symbols = symbols;

			return tag;
		}

		private function decodeTagUnknown(code:uint, length:uint):ITag
		{
			var tag:UnknownTag = new UnknownTag(code)
			tag.bytes = _in.readBytes(length);

			return tag;
		}

		private function decodeTagVideoFrame(length:uint):ITag
		{
			var tag:VideoFrame = new VideoFrame();
			tag.streamId = _in.readUI16();
			tag.frameNumber = _in.readUI16();
			tag.videoData = _in.readBytes(length - 4);

			return tag;
		}

		private function lookupReadTagFunction(code:uint):Function
		{
			switch (code)
			{
				case Tag.END:
					return decodeTagEnd;
					break;
				case Tag.SHOW_FRAME:
					return decodeTagShowFrame;
					break;
				case Tag.DEFINE_SHAPE:
					return decodeTagDefineShape;
					break;
				case Tag.PLACE_OBJECT:
					return decodeTagPlaceObject;
					break;
				case Tag.REMOVE_OBJECT:
					return decodeTagRemoveObject;
					break;
				case Tag.DEFINE_BITS:
					return decodeTagDefineBits;
					break;
				case Tag.DEFINE_BUTTON:
					return decodeTagDefineButton;
					break;
				case Tag.JPEG_TABLES:
					return decodeTagJPEGTables;
					break;
				case Tag.SET_BACKGROUND_COLOR:
					return decodeTagSetBackgroundColor;
					break;
				case Tag.DEFINE_FONT:
					return decodeTagDefineFont;
					break;
				case Tag.DEFINE_TEXT:
					return decodeTagDefineText;
					break;
				case Tag.DO_ACTION:
					return decodeTagDoAction;
					break;
				case Tag.DEFINE_FONT_INFO:
					return decodeTagDefineFontInfo;
					break;
				case Tag.DEFINE_SOUND:
					return decodeTagDefineSound;
					break;
				case Tag.START_SOUND:
					return decodeTagStartSound;
					break;
				case Tag.DEFINE_BUTTON_SOUND:
					return decodeTagDefineButtonSound;
					break;
				case Tag.SOUND_STREAM_HEAD:
					return decodeTagSoundStreamHead;
					break;
				case Tag.SOUND_STREAM_BLOCK:
					return decodeTagSoundStreamBlock;
					break;
				case Tag.DEFINE_BITS_LOSSLESS:
					return decodeTagDefineBitsLossless;
					break;
				case Tag.DEFINE_BITS_JPEG_2:
					return decodeTagDefineBitsJPEG2;
					break;
				case Tag.DEFINE_SHAPE_2:
					return decodeTagDefineShape2;
					break;
				case Tag.DEFINE_BUTTON_CXFORM:
					return decodeTagDefineButtonCxform;
					break;
				case Tag.PROTECT:
					return decodeTagProtect;
					break;
				case Tag.PLACE_OBJECT_2:
					return decodeTagPlaceObject2;
					break;
				case Tag.REMOVE_OBJECT_2:
					return decodeTagRemoveObject2;
					break;
				case Tag.DEFINE_SHAPE_3:
					return decodeTagDefineShape3;
					break;
				case Tag.DEFINE_TEXT_2:
					return decodeTagDefineText2;
					break;
				case Tag.DEFINE_BUTTON_2:
					return decodeTagDefineButton2;
					break;
				case Tag.DEFINE_BITS_JPEG_3:
					return decodeTagDefineBitsJPEG3;
					break;
				case Tag.DEFINE_BITS_LOSSLESS_2:
					return decodeTagDefineBitsLossless2;
					break;
				case Tag.DEFINE_EDIT_TEXT:
					return decodeTagDefineEditText;
					break;
				case Tag.DEFINE_SPRITE:
					return decodeTagDefineSprite;
					break;
				case Tag.PRODUCT_INFO:
					return decodeTagProductInfo;
					break;
				case Tag.FRAME_LABEL:
					return decodeTagFrameLabel;
					break;
				case Tag.SOUND_STREAM_HEAD_2:
					return decodeTagSoundStreamHead2;
					break;
				case Tag.DEFINE_MORPH_SHAPE:
					return decodeTagDefineMorphShape;
					break;
				case Tag.DEFINE_FONT_2:
					return decodeTagDefineFont2;
					break;
				case Tag.EXPORT_ASSETS:
					return decodeTagExportAssets;
					break;
				case Tag.IMPORT_ASSETS:
					return decodeTagImportAssets;
					break;
				case Tag.ENABLE_DEBUGGER:
					return decodeTagEnableDebugger;
					break;
				case Tag.DO_INIT_ACTION:
					return decodeTagDoInitAction;
					break;
				case Tag.DEFINE_VIDEO_STREAM:
					return decodeTagDefineVideoStream;
					break;
				case Tag.VIDEO_FRAME:
					return decodeTagVideoFrame;
					break;
				case Tag.DEFINE_FONT_INFO_2:
					return decodeTagDefineFontInfo2;
					break;
				case Tag.DEBUG_ID:
					return decodeTagDebugID;
					break;
				case Tag.ENABLE_DEBUGGER_2:
					return decodeTagEnableDebugger2;
					break;
				case Tag.SCRIPT_LIMITS:
					return decodeTagScriptLimits;
					break;
				case Tag.SET_TAB_INDEX:
					return decodeTagSetTabIndex;
					break;
				case Tag.FILE_ATTRIBUTES:
					return decodeTagFileAttributes;
					break;
				case Tag.PLACE_OBJECT_3:
					return decodeTagPlaceObject3;
					break;
				case Tag.IMPORT_ASSETS_2:
					return decodeTagImportAssets2;
					break;
				case Tag.DEFINE_FONT_ALIGN_ZONES:
					return decodeTagDefineFontAlignZones;
					break;
				case Tag.CSM_TEXT_SETTINGS:
					return decodeTagCSMTextSettings;
					break;
				case Tag.DEFINE_FONT_3:
					return decodeTagDefineFont3;
					break;
				case Tag.SYMBOL_CLASS:
					return decodeTagSymbolClass;
					break;
				case Tag.METADATA:
					return decodeTagMetadata;
					break;
				case Tag.DEFINE_SCALING_GRID:
					return decodeTagDefineScalingGrid;
					break;
				case Tag.DO_ABC:
					return decodeTagDoABC;
					break;
				case Tag.DO_ABC2:
					return decodeTagDoABC2;
					break;
				case Tag.DEFINE_SHAPE_4:
					return decodeTagDefineShape4;
					break;
				case Tag.DEFINE_MORPH_SHAPE_2:
					return decodeTagDefineMorphShape2;
					break;
				case Tag.DEFINE_SCENE_AND_FRAME_LABEL_DATA:
					return decodeTagDefineSceneAndFrameLabelData;
					break;
				case Tag.DEFINE_BINARY_DATA:
					return decodeTagDefineBinaryData;
					break;
				case Tag.DEFINE_FONT_NAME:
					return decodeTagDefineFontName;
					break;
				case Tag.START_SOUND_2:
					return decodeTagStartSound2;
					break;
				case Tag.DEFINE_BITS_JPEG_4:
					return decodeTagDefineBitsJPEG4;
					break;
				case Tag.DEFINE_FONT_4:
					return decodeTagDefineFont4;
					break;
			}

			return decodeTagUnknown;
		}

		private function notifyError(value:String):void
		{
			if (_errors === null)
			{
				_errors = [];
			}

			_errors[_errors.length] = value;
		}

		private function uncompressSWF(header:Header):Boolean
		{
			//TODO LZMA
			if (header.compressed)
			{
				var pos:int = _in.position;
				var temp:ByteArray = new ByteArray();
				temp.endian = Endian.LITTLE_ENDIAN;
				temp.writeBytes(_in.getBytes(), pos);
				temp.uncompress();
				_in.pushBytes(temp);
				return true;
			}

			return false;
		}
	}
}
